<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <title>生成Mapbox Sprite(精灵图)</title>
    <!-- import CSS -->
    <link href="https://cdn.bootcdn.net/ajax/libs/element-ui/2.15.14/theme-chalk/index.css" rel="stylesheet">
    <style>
        html,
        body,
        #app {
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
            position: absolute;
        }

        .upload,
        .creat {
            border-radius: 4px;
            background: #d3dce6;
            height: 100%;
        }

        .upload,
        .mbSprite {
            overflow-y: auto;
        }

        .but {
            height: 100%;
            padding: 80px 0;
            /* display: flex; */
            /* align-items: center; */
            /* justify-content: center; */
        }
    </style>
</head>

<body>
    <div id="app">
        <el-row style="height: 100%" v-loading="loading" element-loading-text="精灵图拼命生成中..."
            element-loading-spinner="el-icon-loading" element-loading-background="rgba(0, 0, 0, 0.8)">
            <el-col class="upload" :span="12" style="padding: 0 25px;">
                <el-upload multiple action accept="image/*" :auto-upload="false" list-type="picture-card"
                    :on-change="handleChange" :on-preview="handlePreview" :on-remove="handleRemove">
                    <i class="el-icon-plus"></i>
                    <div slot="tip" class="el-upload__tip">只能上传图片格式文件(png/jpeg/svg/ico等), 且单个图片不超过500kb</div>
                </el-upload>
                <el-dialog :visible.sync="dialogVisible">
                    <img width="100%" :src="dialogImageUrl" alt="">
                </el-dialog>
            </el-col>
            <el-col class="but" :span="1">
                <el-row style="height: 5%">图像宽度:</el-row>
                <el-row style="height: 75%">
                    <el-slider v-model="allwidth" :marks="marks" :min="64" :max="1024" vertical height="500px"
                        show-input></el-slider>
                </el-row>
                <el-row style="height: 14%"><el-button type="primary" @click="image">转换</el-button></el-row>
            </el-col>
            <el-col class="creat" :span="11">
                <el-row class="mbSprite" style="height: 50%">
                    <img :src="editURL" style="border:1px solid #6f6f6f">
                </el-row>
                <el-row style="height: 5%">
                    <el-button type="primary" @click="downloadImg">下载图片</el-button>
                    <el-button type="primary" @click="downloadJSON">下载JSON</el-button>
                </el-row>
                <el-row style="height: 30%">
                    <el-input type="textarea" :rows="18" placeholder="JSON内容" v-model="spritejson"></el-input>
                </el-row>
            </el-col>
        </el-row>
    </div>
</body>
<!-- import Vue before Element -->
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.7.9/vue.min.js"></script>
<!-- import JavaScript -->
<script src="https://cdn.bootcdn.net/ajax/libs/element-ui/2.15.14/index.js"></script>
<script>
    new Vue({
        el: '#app',
        data() {
            return {
                allwidth: 512, //生成精灵图的宽度
                marks: {
                    64: '64',
                    128: '128',
                    256: '256',
                    512: '512',
                    1024: '1024',
                },
                loading: false, //加载
                dialogImageUrl: '',
                dialogVisible: false,
                disabled: false,
                fileList: [],
                canvas: [],
                rowparams: [],//List<List<Param>>
                editURL: '',
                spritejson: ''
            }
        },
        mounted() {
        },
        methods: {
            handleRemove(file, fileList) {
                this.fileList = fileList;
            },
            handlePreview(file) {
                this.dialogImageUrl = file.url;
                this.dialogVisible = true;
            },
            async handleChange(file, fileList) {
                //图片文件转base64
                const imgfile = await this.img2base64(file.raw)
                if (imgfile) this.fileList.push(imgfile)
            },
            img2base64(file) {
                return new Promise(function (resolve, reject) {
                    let reader = new FileReader();
                    reader.readAsDataURL(file);
                    reader.onload = function (event) {
                        resolve({
                            name: file.name,
                            url: reader.result
                        })
                    };
                    reader.onerror = function () {
                        reject(null)
                    };
                });
            },
            getImage(file) {
                return new Promise(function (resolve, reject) {
                    let image = new Image();
                    image.src = file.url;
                    image.onload = () => {
                        resolve({
                            image,
                            width: image.width,
                            height: image.height
                        });
                    };
                    image.onerror = function () {
                        reject(null)
                    };
                });
            },
            image() {
                this.loading = true
                const that = this
                let paramlist = [];
                if (this.fileList.length === 0) return;
                const paramPromises = this.fileList.map(async (file) => {
                    const { image, width, height } = await this.getImage(file);
                    let canvas = document.createElement('canvas');
                    canvas.width = width;
                    canvas.height = height;
                    canvas.getContext("2d").drawImage(image, 0, 0);
                    return {
                        name: file.name.split(".")[0],
                        x: 0,
                        y: 0,
                        width: width,
                        height: height,
                        canvas: canvas
                    };
                });
                Promise.all(paramPromises).then(res => {
                    // 过滤为空数组
                    paramlist = res.filter(item => item !== null)
                    paramlist.sort(function (a, b) {
                        return a.height - b.height;
                    });
                    that.creatSprite(paramlist);
                }).catch(error => {
                    console.error("Error processing images: ", error);
                }).finally(() => {
                    that.loading = false
                });
            },
            creatSprite(paramlist) {
                //图片默认宽度为1024
                let allwidth = this.allwidth;
                let rowparams = [], paramnowlist = [];
                let countnum = 0;
                for (let i = 0; i < paramlist.length; i++) {
                    countnum += paramlist[i].width;
                    if (countnum > allwidth) {
                        i = i - 1;
                        countnum = 0;
                        rowparams.push(paramnowlist);
                        paramnowlist = [];
                    } else {
                        paramnowlist.push(paramlist[i]);
                    }
                    if (i === paramlist.length - 1) {
                        rowparams.push(paramnowlist);
                        break;
                    }
                }
                //计算应有的高度
                let allheight = 0;
                rowparams.forEach(item => {
                    allheight += Math.max.apply(Math, item.map(m => m.height));
                })
                //计算应有的宽度
                allwidth = 0
                rowparams[0].forEach(item => {
                    allwidth += item.width;
                })
                if (allwidth > this.allwidth / 2) allwidth = this.allwidth
                else allwidth = Math.pow(2, Math.ceil(Math.log2(allwidth)));
                let spritejson = "{\n";
                //开始画大图
                let editMap = document.createElement('canvas');
                editMap.width = allwidth;
                editMap.height = allheight;
                let editCxt = editMap.getContext("2d");
                let editImageData = editCxt.getImageData(0, 0, allwidth, allheight);
                //保存起始高度
                let heighttemp = 0;
                for (let i = 0; i < rowparams.length; i++) {
                    let tempwidthnum = 0;
                    for (let j = 0; j < rowparams[i].length; j++) {
                        let map = rowparams[i][j].canvas;
                        //循环小图片
                        for (let x = 0; x < map.width; x++) {
                            for (let y = 0; y < map.height; y++) {
                                //获取像素
                                let color = this.getXY(map, x, y);
                                this.setXY(editImageData, x + tempwidthnum, y + heighttemp, color);
                            }
                        }
                        spritejson += "  \"" + rowparams[i][j].name.replace("-", "/").replace("&", ":") + "\":{\"x\":";
                        spritejson += tempwidthnum + ",\"y\":" + heighttemp + ",\"width\":" + rowparams[i][j].width;
                        spritejson += ",\"height\":" + rowparams[i][j].height + ",\"pixelRatio\":1,\"sdf\":false},\n";
                        //增加宽度
                        tempwidthnum += rowparams[i][j].width;
                    }
                    heighttemp += Math.max.apply(Math, rowparams[i].map(m => m.height));
                }
                //保存大图
                editCxt.putImageData(editImageData, 0, 0);
                this.editURL = editMap.toDataURL("image/png");//取得图像的数据URI

                spritejson = spritejson.substring(0, spritejson.lastIndexOf(','));
                spritejson += "\n}";
                this.spritejson = spritejson
            },
            getXY(canvas, x, y) {
                let ctx = canvas.getContext("2d");
                // 在调用getImageData之前设置willReadFrequently属性
                ctx.willReadFrequently = true
                // 获取画布上的图像像素矩阵
                let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
                let w = imageData.width
                let data = imageData.data
                let color = []
                color[0] = data[(y * w + x) * 4]
                color[1] = data[(y * w + x) * 4 + 1]
                color[2] = data[(y * w + x) * 4 + 2]
                color[3] = data[(y * w + x) * 4 + 3]
                return color
            },
            setXY(imageData, x, y, color) {
                let w = imageData.width
                let data = imageData.data
                data[(y * w + x) * 4] = color[0]
                data[(y * w + x) * 4 + 1] = color[1]
                data[(y * w + x) * 4 + 2] = color[2]
                data[(y * w + x) * 4 + 3] = color[3]
                imageData.data = data;
            },
            downloadImg() {
                if (this.editURL === null || this.editURL === '') return;
                if (this.spritejson === null || this.spritejson === '') return;
                // 将图片的src属性作为URL地址
                let a = document.createElement('a')
                let event = new MouseEvent('click')
                a.download = 'sprite@2x'
                a.href = this.editURL
                a.dispatchEvent(event);
            },
            downloadJSON() {
                let pom = document.createElement('a');
                pom.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(this.spritejson));
                pom.setAttribute('download', 'sprite@2x.json');
                if (document.createEvent) {
                    let event = document.createEvent('MouseEvents');
                    event.initEvent('click', true, true);
                    pom.dispatchEvent(event);
                } else {
                    pom.click();
                }
            }
        }
    })
</script>

</html>